#  This file is part of OpenLightingDesigner.
# 
#  OpenLightingDesigner is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  OpenLightingDesigner is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with OpenLightingDesigner.  If not, see <http://www.gnu.org/licenses/>.
#
# EffectsSystem.py
# Copyright (c) 2010 - 2011 Matthew Reid

from Bases import BaseObject, OSCBaseObject, BaseThread, ChildGroup, Category, setID, MasterClock
from sequencer import Sequencer
from snapshot import Snapshot
from effectpalette import EffectPalette
from categorypalette import CategoryPaletteGroup

from macros import MACROS

PALETTE_CATEGORIES = ('Dimmer', 'Beam', 'Color', 'Position')

class EffectsSystem(OSCBaseObject):
    _saved_class_name = 'Effects'
    #_saved_child_objects = ['EffectPalettes', 'Faders']
    _saved_child_objects = ['CategoryPalettes']
    _ChildGroups = {'EffectPalettes':dict(child_class=EffectPalette, ignore_index=True), 
                    'AppliedPalettes':dict(child_class=EffectPalette, ignore_index=True), 
                    'Faders':{}, 
                    'Shuttles':{}, 
                    'CategoryPalettes':dict(child_class=CategoryPaletteGroup)}
    def __init__(self, **kwargs):
        self.saved_child_classes = [EffectPalette]
        self.root_category = Category(name='effects_root', id='effects_root')
        self.MainController = kwargs.get('MainController')
        self.DeviceSystem = self.MainController.DeviceSystem
        super(EffectsSystem, self).__init__(**kwargs)
        archive = self.MainController.Archive
        for key in self.saved_child_objects:
            archive.add_member(name=key, 
                               path='EffectsSystem', 
                               filename='%s.js' % (key), 
                               serialize_obj={'EffectsSystem':self}, 
                               serialize_kwargs={'EffectsSystem':{'saved_child_objects':[key]}})
#        self.CategoryPalettes = ChildGroup(name='CategoryPalettes', 
#                                           child_class=CategoryPaletteGroup, 
#                                           osc_parent_node=self.osc_node, 
#                                           osc_address='CategoryPalettes')
        self.clock = kwargs.get('clock')
        if not self.clock:
            self.clock = MasterClock()
        self.clock.start()
        self.Sequencers = {}
        if 'deserialize' not in kwargs:
            #self.Palettes = {}
            #self.EffectPalettes = {}
            #self.AppliedPalettes = {}
            p = self.add_palette(id='Fade', name='Fade', all_groups=True)
            e = p.add_effect(controller_type='Keyframe', name='Fade', id='Fade')
            #e.controller_prototype.add_keyframes({0.0:0.0, 1.0:1.0})
            #self.Faders = {}
            p = self.add_palette(id='Shuttle', name='Shuttle')
            e = p.add_effect(controller_type='Functional', name='Shuttle', id='Functional')
            #self.Shuttles = {}
            category = self.DeviceSystem.root_category.subcategories['attribute']
            for key in PALETTE_CATEGORIES:
                self.CategoryPalettes.add_child(name=key, 
                                                attrib_category=category, 
                                                MainController=self.MainController, 
                                                EffectsSystem=self, 
                                                osc_address=key)
        self.Macros = {}
        for key, val in MACROS.iteritems():
            m = val(MainController=self.MainController)
            self.Macros[m.name] = m
        self.dump_request_thread = None
        self.add_osc_handler(callbacks={'request-dump':self.on_osc_request_dump})
        self.osc_node.send_interrupt_recursive = True
        self.osc_node.children['request-dump'].send_interrupt_recursive = False
        
    def add_palette(self, **kwargs):
        '''
        :Parameters:
            id :
            name :
            
        '''
        kwargs.setdefault('root_category', self.root_category)
        #p = EffectPalette(**kwargs)
        p = self.EffectPalettes.add_child(**kwargs)
        return p
        
    def del_palette(self, **kwargs):
        pid = kwargs.get('id')
        palette = kwargs.get('palette')
        if not palette:
            palette = self.EffectPalettes.get(pid)
        palette.unlink()
        self.EffectPalettes.del_child(palette)
        
    def add_fader(self, **kwargs):
        '''
        :Parameters:
            id :
            name :
            duration :
        '''
        kwargs.update({'palette_id':'Fade', 'loop_mode':'single', 'auto_start':False})
        f = self.apply_palette(**kwargs)
        self.Faders.add_child(existing_object=f)
        return f
        
    def edit_fader(self, **kwargs):
        '''
        :Parameters:
            fader :
            fader_id :
            objects : {'objid':{'obj':obj, 'start':startval, 'end':endval}}
        '''
        fader_id = kwargs.get('fader_id')
        fader = kwargs.get('fader')
        objects = kwargs.get('objects')
        if not fader and fader_id is not None:
            fader = self.Faders[fader_id]
        e = fader.effects['Fade']
        for key, val in objects.iteritems():
            if key not in fader.obj_ids:
                fader.add_obj(val['obj'])
            c = e.controllers[key]['Dimmer']
            start = val['start'] / c.obj_scale_factor
            end = val['end'] / c.obj_scale_factor
            c.add_keyframes({0.0:start, 1.0:end})
            
    def del_fader(self, **kwargs):
        id = kwargs.get('id')
        fader = kwargs.get('fader')
        if not id and fader is not None:
            id = fader.id
        if not fader:
            fader = self.Faders[id]
        self.clear_palette(palette=fader)
        self.Faders.del_child(fader)
        #if id in self.Faders:
        #    del self.Faders[id]
        
    def add_shuttle(self, **kwargs):
        '''
        :Parameters:
            id :
            name :
            duration :
            group_names :
        '''
        kwargs.update({'palette_id':'Shuttle', 'loop_mode':'up', 'auto_start':False})
        s = self.apply_palette(**kwargs)
        for name in kwargs.get('group_names', []):
            s.add_group_name(name)
        #self.Shuttles.update({s.id:s})
        self.Shuttles.add_child(existing_object=s)
        return s
    
    def add_sequencer(self, **kwargs):
        '''
        :Parameters:
            id :
            name :
            duration : float, duration in seconds
            mode : str, loop mode ('single', 'updn', 'up', 'dn')
        '''
        kwargs.setdefault('root_category', self.root_category)
        kwargs.setdefault('clock', self.clock)
        sq = Sequencer(**kwargs)
        sq.connect('state_changed', self.on_sequencer_state_changed)
        self.Sequencers.update({sq.id:sq})
        return sq
        
    def del_sequencer(self, **kwargs):
        id = kwargs.get('id')
        sq = kwargs.get('sequencer')
        if not sq:
            sq = self.Sequencers.get('id')
        sq.stop()
        if sq.id in self.Sequencers:
            del self.Sequencers[sq.id]
            #print 'sequencer deleted'
        
    def apply_palette(self, **kwargs):
        '''
        :Parameters:
            id :
            name :
            palette_id :
            palette :
            loop_mode :
            duration :
            objects : list of groups or attributes
        '''
        id = kwargs.get('id')
        palette_id = kwargs.get('palette_id')
        palette = kwargs.get('palette')
        sq = kwargs.get('sequencer')
        mode = kwargs.get('loop_mode', 'updn')
        objects = kwargs.get('objects', [])
        auto_start = kwargs.get('auto_start', True)
        new_kwargs = kwargs.copy()
        for key in ['palette_id', 'palette', 'loop_mode', 'objects']:
            if key in new_kwargs:
                del new_kwargs[key]
        if not palette and palette_id is not None:
            palette = self.EffectPalettes.get(palette_id)
        if palette:
            duration = kwargs.get('duration', palette.default_duration)
            if sq is None:
                sq = self.add_sequencer(duration=duration, mode=mode)
            p_kwargs = palette._get_saved_attr()['attrs'].copy()
            p_kwargs['obj_ids'].clear()
            p_kwargs.update({'sequencer':sq, 'prototype':palette})
            p_kwargs.update(new_kwargs)
            #new_p = EffectPalette(**p_kwargs)
            new_p = self.AppliedPalettes.add_child(**p_kwargs)
            palette.connect('settings_changed', new_p.on_settings_changed)
            for obj in objects:
                new_p.add_obj(obj)
            if auto_start:
                sq.start()
            #self.AppliedPalettes.update({new_p.id:new_p})
        return new_p
        
    def clear_palette(self, **kwargs):
        id = kwargs.get('id')
        palette = kwargs.get('palette')
        if not palette and id is not None:
            palette = self.AppliedPalettes.get(id)
        if palette:
            #palette.unlink()
            #palette.sequencer.stop()
            #if palette.id in self.AppliedPalettes:
            #    del self.AppliedPalettes[palette.id]
            sq = palette.sequencer
            self.AppliedPalettes.del_child(palette)
            if sq.stopping:
                sq.stopped.wait()
                sq.remove_controllers()
            if not len(sq.controller_set):
                self.del_sequencer(sequencer=sq)
            
            #print 'palette deleted'
            
    def request_osc_updates(self):
        if self.dump_request_thread is not None:
            self.dump_request_thread.stop()
            self.dump_request_thread = None
        self.dump_request_thread = OSCDumpRequest(EffectsSystem=self)
        self.dump_request_thread.bind(dump_complete=self.on_dump_request_thread_dump_complete)
        self.dump_request_thread.start()
        return self.dump_request_thread
        
    def on_dump_request_thread_dump_complete(self, **kwargs):
        self.dump_request_thread = None
        
    def load_osc_dump(self, dump):
        self.osc_node.send_interrupt_enabled = True
        keys = sorted(dump.keys())
        for key in keys:
            self.from_json(string=dump[key])
        self.osc_node.send_interrupt_enabled = False
        
    def on_osc_request_dump(self, **kwargs):
        if self.osc_node.oscMaster:
            values = kwargs.get('values')
            client = kwargs.get('client')
            #print 'main...client = ', client
            if len(values):
                for i, key in enumerate(values):
                    s = self.to_json(saved_child_objects=[key], indent=None)
                    self.osc_node.send_message(address='dump-response', value=[i, s], client=client)
            else:
                s = self.to_json(indent=None)
                self.osc_node.send_message(address='dump-response', value=[0, s], client=client)
            self.osc_node.send_message(address='dump-response', value='dump-complete', client=client)
        
    def clear_show(self, set_interrupts=False):
        if set_interrupts:
            self.osc_node.send_interrupt_enabled = True
        for key in self.AppliedPalettes.keys()[:]:
            self.clear_palette(id=key)
        for palettes in self.CategoryPalettes.itervalues():
            for palette in palettes.itervalues():
                palette.stored_values.clear()
                palette.name = str(palette.Index)
        if set_interrupts:
            self.osc_node.send_interrupt_enabled = False
        
    def load_globals(self, **kwargs):
        pass
    def save_globals(self, **kwargs):
        return {}
        
    def get_file_data(self, **kwargs):
        d = {}
        for key in ['CategoryPalettes']:
            filename = '.'.join([key, 'js'])
            d[filename] = self._get_saved_attr(saved_child_objects=[key])
        return d
        
    def shutdown(self):
        for pid in self.AppliedPalettes.keys()[:]:
            self.clear_palette(id=pid)
        for sid in self.Sequencers.keys()[:]:
            sq = self.Sequencers[sid]
            if sq.stopping:
                continue
            sq.stop(blocking=True)
        self.clock.stop(blocking=True)
    @property
    def fps(self):
        return self.clock.fps
        
    def on_sequencer_state_changed(self, **kwargs):
        obj = kwargs.get('obj')
        state = kwargs.get('state')
        if not state:
            if obj.id in self.Sequencers and not obj.persistent and len(obj.controllers) == 0:
                self.del_sequencer(sequencer=obj)
        
class OSCDumpRequest(BaseThread):
    _Events = {'response_complete':{}}
    def __init__(self, **kwargs):
        kwargs['thread_id'] = 'EffectsSystem_dump_request'
        super(OSCDumpRequest, self).__init__(**kwargs)
        self.register_signal('dump_complete')
        self.EffectsSystem = kwargs.get('EffectsSystem')
        self._osc_dumps_pending = {}
        
    def run(self):
        self._running.set()
        es = self.EffectsSystem
        self._osc_dumps_pending.clear()
        node = es.osc_node.add_child(name='dump-response')
        node.bind(message_received=self.on_dump_response)
        self.dump_node = node
        objnames = ['CategoryPalettes']
        es.osc_node.send_message(address='request-dump', value=objnames, to_master=True)
        self.response_complete.wait()
        if self._running:
            self.emit('dump_complete')
            self.stop()
        
    def stop(self, **kwargs):
        blocking = kwargs.get('blocking')
        self.dump_node.unbind(self)
        self._running.clear()
        self.response_complete.set()
        self._stopped.set()
        if blocking:
            self.join()
        
    def on_dump_response(self, **kwargs):
        message = kwargs.get('message')
        values = message.get_arguments()
        if values[0] == 'dump-complete':
            self.EffectsSystem.load_osc_dump(self._osc_dumps_pending)
            self.response_complete.set()
            return
        self._osc_dumps_pending[values[0]] = values[1]
